package com.ibm.framework.uts.util;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.Proxy;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collections;
import java.util.List;
import java.util.zip.GZIPInputStream;

/**
 * @author <a href="mailto:ljw79618@gmail.com">L.J.W</a>
 */
public abstract class HttpClient {

    /**
     * 初始化字节大小
     */
    private static final int SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE = 1024;

    /**
     * 内容类型：java序列化对象
     */
    public static final String CONTENT_TYPE_SERIALIZED_OBJECT = "application/x-java-serialized-object";

    /**
     * http请求方式post
     */
    private static final String HTTP_METHOD_POST = "POST";

    /**
     * http头内容类型
     */
    private static final String HTTP_HEADER_CONTENT_TYPE = "Content-Type";

    /**
     * http头内容大小
     */
    private static final String HTTP_HEADER_CONTENT_LENGTH = "Content-Length";

    /**
     * http头内容编码方式
     */
    private static final String HTTP_HEADER_CONTENT_ENCODING = "Content-Encoding";

    /**
     * 文件压缩方式
     */
    private static final String ENCODING_GZIP = "gzip";

    /**
     * post发送请求
     * 
     * @param urlList 请求列表
     * @param data 请求数据
     * @param timeout 超时时间
     * @return 请求返回结果
     * @throws Exception 异常
     */
    public static Object sendByPost(List<String> urlList, Object data, int timeout) throws Exception {
        return sendByPost(urlList, data, timeout, 1, 0, null);
    }

    /**
     * post发送请求
     * 
     * @param urlList 请求列表
     * @param data 请求数据
     * @param timeout 超时时间
     * @param retry 重试次数
     * @param interval 间隔时间
     * @return 请求返回结果
     * @throws Exception 异常
     */
    public static Object sendByPost(List<String> urlList, Object data, int timeout, int retry, int interval)
            throws Exception {
        return sendByPost(urlList, data, timeout, retry, interval, null);
    }

    /**
     * post发送请求
     * 
     * @param urlList 请求列表
     * @param data 请求数据
     * @param timeout 超时时间
     * @param retry 重试次数
     * @return 请求返回结果
     * @throws Exception 异常
     */
    public static Object sendByPost(List<String> urlList, Object data, int timeout, int retry) throws Exception {
        return sendByPost(urlList, data, timeout, retry, 0, null);
    }

    /**
     * post发送请求
     * 
     * @param urlList 请求列表
     * @param data 请求数据
     * @param timeout 超时时间
     * @param retry 重试次数
     * @param interval 重试间隔
     * @param callback 重试回调方法
     * @return 请求返回结果
     * @throws Exception 异常
     */
    public static Object sendByPost(List<String> urlList, Object data, int timeout, int retry, int interval,
            RetryCallBack callback) throws Exception {
        Collections.shuffle(urlList);
        Exception exception = null;
        for (int i = 0; i < retry; i++) {
            try {
                Object newData = data;
                if (i != 0 && callback != null) {
                    newData = callback.onRetry(data);
                }
                String url = urlList.get(i % urlList.size());
                ByteArrayOutputStream baos = getByteArrayOutputStream(newData);
                /**
                 * Not use proxy to prevent: Software caused connection abort: socket write error
                 */
                URLConnection connection = new URL(url).openConnection(Proxy.NO_PROXY);
                if (!(connection instanceof HttpURLConnection)) {
                    exception = new Exception("Service URL [" + url + "] is not an HTTP URL");
                    break;
                }
                HttpURLConnection con = (HttpURLConnection) connection;
                prepareConnection(con, baos.size(), timeout);
                writeRequestBody(con, baos);
                validateResponse(con);
                InputStream responseBody = readResponseBody(con);
                return readResult(responseBody);
            } catch (Exception ex) {
                exception = ex;
                /** If IOException, retry */
                if (!(ex instanceof IOException)) {
                    break;
                }
                if (interval > 0) {
                    Thread.sleep(interval);
                }
            }
        }
        throw exception;
    }

    /**
     * 重发回调
     * 
     * @author v_pinzhao
     * 
     */
    public interface RetryCallBack {

        /**
         * can modify data on each retry.
         * 
         * @param data 数据
         * @return 修改结果
         */
        public Object onRetry(Object data);

    }

    /**
     * 
     * @param data 请求数据
     * @return 字节数组
     * @throws IOException 异常
     */
    private static ByteArrayOutputStream getByteArrayOutputStream(Object data) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(SERIALIZED_INVOCATION_BYTE_ARRAY_INITIAL_SIZE);
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        try {
            oos.writeObject(data);
        } finally {
            oos.close();
        }
        return baos;
    }

    /**
     * 
     * @param connection 连接
     * @param contentLength 内容长度
     * @param timeout 超时时间
     * @throws IOException 异常
     */
    private static void prepareConnection(HttpURLConnection connection, int contentLength, int timeout)
            throws IOException {
        if (timeout >= 0) {
            connection.setConnectTimeout(timeout);
        }
        if (timeout >= 0) {
            connection.setReadTimeout(timeout);
        }
        connection.setDoOutput(true);
        connection.setRequestMethod(HTTP_METHOD_POST);
        connection.setRequestProperty(HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_SERIALIZED_OBJECT);
        connection.setRequestProperty(HTTP_HEADER_CONTENT_LENGTH, Integer.toString(contentLength));
    }

    /**
     * 
     * @param con http连接
     * @param baos 请求数据
     * @throws IOException 异常
     */
    private static void writeRequestBody(HttpURLConnection con, ByteArrayOutputStream baos) throws IOException {
        baos.writeTo(con.getOutputStream());
    }

    /**
     * 验证请求反馈
     * 
     * @param con http请求
     * @throws IOException 异常
     */
    private static void validateResponse(HttpURLConnection con) throws IOException {

        if (con.getResponseCode() >= 300) {
            throw new IOException("Did not receive successful HTTP response: status code = " + con.getResponseCode()
                    + ", status message = [" + con.getResponseMessage() + "]");
        }
    }

    /**
     * 
     * @param con 请求连接
     * @return 返回结果
     * @throws IOException 异常
     */
    private static InputStream readResponseBody(HttpURLConnection con) throws IOException {
        if (isGzipResponse(con)) {
            /** GZIP response found - need to unzip. */
            return new GZIPInputStream(con.getInputStream());
        } else {
            /** Plain response found. */
            return con.getInputStream();
        }
    }

    /**
     * 
     * @param con 请求连接
     * @return 是否是GIZP 返回结果
     */
    private static boolean isGzipResponse(HttpURLConnection con) {
        String encodingHeader = con.getHeaderField(HTTP_HEADER_CONTENT_ENCODING);
        return (encodingHeader != null && encodingHeader.toLowerCase().contains(ENCODING_GZIP));
    }

    /**
     * 
     * @param is 返回的结果流
     * @return 结果
     * @throws IOException 异常
     * @throws ClassNotFoundException 异常
     */
    private static Object readResult(InputStream is) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(is);
        try {
            return ois.readObject();
        } finally {
            ois.close();
        }
    }
}
